import sys, re, collections, os, StringIO, copy, textwrap, pprint

def splitcode(s):
    """Given a string or list of strings representing "C" expressions, return a list of expressions."""
    if not s:
        return []
    if isinstance(s, str):
        s = [s]
    exprs = []
    for ss in s:
        exprs += [x for x in [x.strip() for x in ss.split(';')] if x]
    return exprs

# I like wide code.
COLUMNS = 120  
def pretty_fill(s, indent=0):
    return textwrap.fill(s, width=COLUMNS, initial_indent=' '*indent, subsequent_indent=' '*indent)
def pretty_code(s, indent=0):
    "Return the input folded to fit in the available space."
    if len(s) > COLUMNS - 20:
        return '\n' + pretty_fill(s, indent) + '\n'
    else:
        return ' ' + s + ' '

class OutputFormatter:
    """Abstract base class for a class that takes a model and generates one or more output files. It takes care not to
        touch the file(s) if the new contents are identical. It deletes the file(s) on error."""
    # List of default filenames, also defines number of output files and their extensions.
    DEFAULT_FILENAMES = ('output.txt',)
    STREAMS = ('DEFAULT',)

    # Define various macros that the formatter uses. These can be overridden if required.
    SYMBOL_DEFINITIONS = {}
    
    RE_SYMBOL = re.compile(r'\$\(([a-z_]+)\)', re.I)
    @staticmethod
    def sub(s, symbols):
        "Perform macro substitution from dict symbols into s, where macros are called as $(foo)."
        while 1:
            s, n = OutputFormatter.RE_SYMBOL.subn(lambda m: symbols[m.group(1)], s)
            if not n:
                return s
    
    def __init__(self, path, options, extra_symbol_defs={}):
        """Initialise with a path. If the Formatter has more than 1 output file, the supplied extension (if any) is 
            removed and the extensions from the DEFAULT_FILENAMES member are used instead."""
        self.options = options
        self.indent=0
        print path
        self.filepaths = self._get_filepaths(path)
        print self.filepaths
        self.ofs = []
        assert len(self.filepaths) == len(self.STREAMS)
        for i, s in enumerate(self.STREAMS):
            setattr(self, s, i)
            self.ofs.append(StringIO.StringIO())
        
        # Build a dict of symbol definitions that we use.
        self.symbol_definitions = copy.deepcopy(self.SYMBOL_DEFINITIONS)
        self.symbol_definitions.update(extra_symbol_defs)

        # Build a dict of symbols with default values.
        self.symbols = dict([(n, v[0]) for (n, v) in self.symbol_definitions.items()])
    def _get_filepaths(self, path):
        "Return a list of output file paths."
        if not path: # Give a default filename.
            return self.DEFAULT_FILENAMES
        if len(self.DEFAULT_FILENAMES) > 1 or not os.path.splitext(path)[1]: # Frob the extensions.
            return tuple([os.path.splitext(path)[0] + os.path.splitext(p)[1] for p in self.DEFAULT_FILENAMES])
        return (path,)
    def blurt(self, msg):
        if self.options.verbosity: 
            sys.stdout.write(msg + '\n')
    def write(self, s, stream=0, indent=0, dedent=0, flushleft=0):
        if indent:
            self.indent += 4
        if dedent:
            self.indent -= 4
        indentstr = '' if flushleft else ' '*self.indent
        for ln in OutputFormatter.sub(s, self.symbols).splitlines():
            self.ofs[stream].write(indentstr + ln + '\n')
    def close(self):
        for filepath, of in zip(self.filepaths, self.ofs):
            outstr = of.getvalue()
            of.close()
            try:
                existing = open(filepath, 'rt').read()
            except StandardError:
                existing = None
            if existing != outstr:
                open(filepath, 'wt').write(outstr)
                self.blurt('Wrote file `%s`.' % filepath)
            else:
                self.blurt('File `%s` already exists.' % filepath)
    def abort(self):
        for filepath in self.filepaths:
            if os.path.exists(filepath):
                try:
                    os.remove(filepath)
                    self.blurt('Deleted file `%s`.' % filepath)
                except OSError:
                    self.blurt('Failed to delete file `%s`.' % filepath)
    def generate(self, model, nmgr, options):
        raise NotImplementedError 

class Formatter_XML(OutputFormatter):
    DEFAULT_FILENAMES = ('output.xml',)
    STREAMS = ('DEFAULT',)
    def __init__(self, path, options, extra_symbol_defs=None):
        if extra_symbol_defs is None:
            extra_symbol_defs = {}
        OutputFormatter.__init__(self, path, options, extra_symbol_defs) 
    def generate(self, model):
        model['.machine']._toxml(self.ofs[self.DEFAULT])
        
class Formatter_C(OutputFormatter):
    SYMBOL_DEFINITIONS = collections.OrderedDict((
        ('EVENT_ACCESSOR', (
          '(ev)',
          '''The code fragment used to access the event ID in the body of the process() function from the event instance.
             The event is in variable 'ev'.'''
        )),
        ('EVENT_REFERENCE_TYPE', (
          'smk_$(MACHINE_NAME)_event_t',
          '''The code fragment used to reference the event type, as used in function definitions'''
        )),
        ('STATE_NAME_PREFIX', (
          'ST_$(MACHINE_NAME_UC)_',
          '''Macro used to form explicit state names in the source files.'''
        )),
        ('STATE_TYPE', (
          'uint8_t',
          '''Type used to hold the state variable. Should be an efficient integer type, usually uint8_t or int.'''
        )),
    ))
    EXTRA_SYMBOL_DEFINITIONS = {}
    DEFAULT_FILENAMES = ('output.h', 'output.c')
    STREAMS = ('HEADER', 'SOURCE')
    def __init__(self, path, options, extra_symbol_defs=None):
        if extra_symbol_defs is None:
            extra_symbol_defs = self.EXTRA_SYMBOL_DEFINITIONS
        OutputFormatter.__init__(self, path, options, extra_symbol_defs) 
    def assert_transition_map_is_valid(self, transmap):
        """Transmap is a list of (guard, [actions], target). To generate valid code we assert that the first N-1 
            items in the list have guards (the last item can have a guard or not, we don't care."""
        for guard, actions, target in transmap[:-1]:
            assert guard, "Unguarded transition found in first N-1 items of %s." % pprint.pformat(transmap, width=120)
    def mk_state_name(self, st_name):
        return '$(STATE_NAME_PREFIX)' + st_name.upper()
    def mk_event_name(self, ev_name):
        return ev_name.upper()
    def generate(self, model):
        self.symbols['MACHINE_NAME'] = model['.machine'].name
        self.symbols['MACHINE_NAME_UC'] = model['.machine'].name.upper()

        # Process verbatim sections in machine declaration.
        for elementname, symbolname in (('include', 'VERBATIM_INCLUDE'), ('code', 'VERBATIM_CODE')):
            content = getattr(model['.machine'], elementname)
            if content:
                source_code = '/* Verbatim %s code. */\n%s' % (elementname, content)
                self.symbols[symbolname] = source_code
            else:
                self.symbols['symbolname'] = ''
            
        self.symbols['CONTEXT_DECL'] = \
          '\n'.join(['    %s;' % x for x in ['$(STATE_TYPE) state_'] + splitcode(model['.machine'].property_list)])
        reset_actions, reset_state = model['.machine'].get_init_actions_state()
        self.symbols['STATE_DECL'] = \
          pretty_code(', '.join([self.mk_state_name(x) for x in model['.machine'].state_map]), indent=4)
        self.symbols['RESET_FUNCTION_BODY'] = '\n'.join(['    %s;' % x for x in splitcode(reset_actions)])
        self.symbols['INITIAL_STATE'] = reset_state.name.upper()

        self.write(self.HEADER_TEMPLATE, stream=self.HEADER)
        self.write(self.SOURCE_TEMPLATE_START, stream=self.SOURCE)
        
        # Write main nested switch statement body.
        self.indent += 4
        for st_name, evdict in model.items():
            if st_name.startswith('.'):
                continue
            self.write('case %s:' % self.mk_state_name(st_name), stream=self.SOURCE)
            self.indent += 4
            self.write('switch($(EVENT_ACCESSOR)) {', stream=self.SOURCE)
            
            for ev_name, event_defs in evdict.items():
                self.write('case %s:' % self.mk_event_name(ev_name), stream=self.SOURCE)
                self.indent += 4
                
                # Check if the only action for this handler is a goto,
                if isinstance(event_defs, str):
                    self.write(event_defs, stream=self.SOURCE)
                    self.indent -= 4
                else:
                    self.assert_transition_map_is_valid(event_defs)
                    
                    # Add label if this handler is targetted by a goto:
                    try:
                        label = model['.goto_labels'][st_name][ev_name]
                        self.write(label, stream=self.SOURCE, flushleft=1)
                    except KeyError:
                        pass
                            
                    for trans_index, trans_def in enumerate(event_defs):
                        guard, actions, target = trans_def
                        if guard:
                            if trans_index == 0:
                                self.write('if(%s) {' % guard, stream=self.SOURCE)
                            else:   
                                self.write('else if(%s) {' % guard, stream=self.SOURCE)
                            self.indent += 4
                        else:
                            if trans_index > 0:
                                self.write('else {', stream=self.SOURCE)
                                self.indent += 4
                                    
                        # Emit all actions...
                        if 'no-prettify' in model['.machine'].options.content:
                            self.write('\n'.join(actions), stream=self.SOURCE)
                        else:
                            for a in splitcode(actions):
                                if not a.endswith(';'):
                                    a = a + ';'
                                m = re.match(r'(\w+:)\s*(.*)$', a, re.I)
                                if m:
                                    self.write('%-*s%s' % (self.indent, m.group(1), m.group(2)), stream=self.SOURCE, flushleft=1)
                                else:
                                    self.write(a, stream=self.SOURCE)
                    
                        if guard or trans_index > 0:
                            self.indent -= 4
                            self.write('}', stream=self.SOURCE)

                    self.indent -= 4
                    self.write('break;', stream=self.SOURCE)
            self.write('}', stream=self.SOURCE)
            self.write('break;', stream=self.SOURCE, dedent=1)
        self.indent -= 4
            
        self.write(self.SOURCE_TEMPLATE_END, stream=self.SOURCE, dedent=1)

class Formatter_C_StaticContext(Formatter_C):
    HEADER_TEMPLATE = """\
/* This file is auto-generated. Do not edit. */

/* Reset the machine. */
void smk_reset_$(MACHINE_NAME)(void);

/* Pass an event to the machine. */
void smk_process_$(MACHINE_NAME)($(EVENT_REFERENCE_TYPE) ev);

/* EOF */
"""
    SOURCE_TEMPLATE_START = """\
/* This file is auto-generated. Do not edit. */

$(VERBATIM_INCLUDE)
/* Context type declaration */
typedef struct {
$(CONTEXT_DECL)
} smk_context_$(MACHINE_NAME)_t;

/* State ID declaration. */
enum {$(STATE_DECL)};

static smk_context_$(MACHINE_NAME)_t context_;

/* Some handy macros. */
#define PROP(member_) (context.(member_))
#define SMK_CHANGE_STATE(st_) (PROP(state_) = (st_))

$(VERBATIM_CODE)

void smk_reset_$(MACHINE_NAME)(void) {
    SMK_CHANGE_STATE($(STATE_NAME_PREFIX)$(INITIAL_STATE));
$(RESET_FUNCTION_BODY)
}

void smk_process_$(MACHINE_NAME)($(EVENT_REFERENCE_TYPE) event) {
    switch(context_.state_) {
"""
    SOURCE_TEMPLATE_END = """\
    }
}

/* EOF */
"""
    def __init__(self, path, options):
        Formatter_C.__init__(self, path, options)

class Formatter_C_MultiContext(Formatter_C):
    HEADER_TEMPLATE = """\
/* This file is auto-generated. Do not edit. */

/* Context type declaration */
typedef struct {
$(CONTEXT_DECL)
} smk_context_$(MACHINE_NAME)_t;

/* Reset the machine. */
void smk_reset_$(MACHINE_NAME)(smk_context_$(MACHINE_NAME)_t* context);

/* Pass an event to the machine. */
void smk_process_$(MACHINE_NAME)(smk_context_$(MACHINE_NAME)_t* context, $(EVENT_REFERENCE_TYPE) event);

/* EOF */
"""
    SOURCE_TEMPLATE_START = """\
/* This file is auto-generated. Do not edit. */

/* State ID declaration. */
enum {$(STATE_DECL)};

/* Some handy macros. */
#define PROP(member_) (context->(member_))
#define SMK_CHANGE_STATE(st_) (PROP(state_) = (st_))

$(VERBATIM_CODE)

void smk_reset_$(MACHINE_NAME)($(CONTEXT_REFERENCE_TYPE) context) {
    SMK_CHANGE_STATE($(STATE_NAME_PREFIX)$(INITIAL_STATE));
$(RESET_FUNCTION_BODY)
}

void smk_process_$(MACHINE_NAME)($(CONTEXT_REFERENCE_TYPE) context, $(EVENT_REFERENCE_TYPE) event) {
    switch(context_.state_) {
"""
    SOURCE_TEMPLATE_END = """\
    }
}

/* EOF */
"""
    EXTRA_SYMBOL_DEFINITIONS = collections.OrderedDict((
        ('CONTEXT_REFERENCE_TYPE', (
          'const smk_context_$(MACHINE_NAME)_t*',
          '''The code fragment used to reference the context type, as used in function definitions'''
        )),
    ))
    def __init__(self, path, options):
        Formatter_C.__init__(self, path, options)
